The European Union (EU) is often criticized for a heavy regulatory touch. What is the reality? This automatically updated research document gives a real-time overview of the EU’s legislative output.

As of 03 June 2022, the EU and its predecessors have produced 139757 regulations, 4255 directives, 35734 decisions and 1299 recommendations, according to Eur-Lex data pulled via the eurlex package for R and a SPARQL API maintained by the Publications Office of the European Union.

Number of acts over time

We start by looking at how the number of the four main legal acts changed over the lifespan of the EU.

Plot

Asd

Code

# select only main act types
relevant_acts <- bind_rows(Regulation = regs, Decison = decs, Directive = dirs, Recommendation = recs,
                        .id = "type") |> 
  mutate(year = as.integer(str_sub(celex, 2,5))) |> 
  mutate(month_year = as.Date(str_replace(date, "[:digit:][:digit:]$", "01")))

# count
n_relevant <- relevant_acts |> 
  count(year, type)

# viz
ggplot(n_relevant, aes(x = year, y = n, fill = type, color = type)) +
  geom_col(show.legend = FALSE) +
  scale_fill_brewer(palette = "Spectral") +
  scale_color_brewer(palette = "Spectral") +
  facet_wrap(~type, scale = "free_y") +
  theme_minimal() +
  theme(legend.position = "top",
        legend.justification = "left",
        legend.title = element_text(face = "italic"),
        plot.background = element_rect(fill = "white"),
        panel.grid = element_line(color = "grey95"),
        axis.text = element_text(color = "grey10"),
        title = element_text(face = "bold"),
        strip.text = element_text(hjust = 0, face = "bold")) +
  labs(x = NULL,
       y = "Number of acts",
       title = "Number of legal acts produced by the European Union",
       subtitle = "Aggregated at the year level by date of publication")

Dataframe

Asd

Proportion of act types

So we see there is significant over-time variation in the use of the different types of legal acts, but some types of acts, notably regulations, have been historically significantly more common than other types of acts. We can visualize the evolution of proportions directly.

Plot

Etc..

Code

# create df
proptime_df <- expand.grid(year = min(n_relevant$year):max(n_relevant$year),
            type = unique(n_relevant$type)) |> 
  left_join(n_relevant) |> 
  mutate(n = ifelse(is.na(n), 0L, n)) |> 
  group_by(year) |> 
  mutate(total_y = sum(n),
         proportion = n/total_y) |> 
  ungroup()

# viz
proptime_df |> 
  ggplot(aes(x = year, y = proportion, fill = type, color = type)) +
  geom_area(show.legend = TRUE) +
  geom_vline(xintercept = c(1960,1980,2000,2020), lty = 2, color = "grey70") +
  scale_fill_brewer(palette = "Spectral") +
  scale_color_brewer(palette = "Spectral") +
  theme_minimal() +
  theme(legend.position = "top",
        legend.justification = "left",
        legend.title = element_text(face = "italic"),
        plot.background = element_rect(fill = "white"),
        panel.grid = element_blank(),
        axis.text = element_text(color = "grey10"),
        title = element_text(face = "bold"),
        strip.text = element_text(hjust = 0, face = "bold")) +
  scale_x_continuous(expand = c(0,0)) +
  scale_y_continuous(expand = c(0,0)) +
  labs(x = NULL,
       y = NULL,
       title = "Proportion of each type of legal act by year")

Dataframe

Asd

Year-on-year comparison

How does the current state of the EU’s legislative output compare to previous years?

Plot

Code

# monthly output last two years
last_two_years <- relevant_acts |> 
  mutate(year = as.integer(str_sub(date, 1,4))) |> 
  filter(year %in% c(max(relevant_acts$year), max(relevant_acts$year)-1)) |> 
  count(month_year, type)

last_two_years_full <- expand.grid(month_year = unique(last_two_years$month_year),
                                   type = unique(last_two_years$type)) |> 
  left_join(last_two_years) |> 
  mutate(n = ifelse(is.na(n), 0L, n))

# year on year change
last_two_yoy_diff <- last_two_years_full |> 
  arrange(month_year) |> 
  mutate(month = str_sub(month_year, 6,7)) |> 
  group_by(month, type) |> 
  summarise(yoy_diff = diff(n)) |> 
  ungroup()

# viz
last_two_yoy_diff |> 
  ggplot(aes(x = month, y = yoy_diff, fill = type, color = type)) +
  geom_col(show.legend = FALSE) +
  geom_hline(yintercept = 0, color = "grey83", lty = 1) +
  facet_wrap(~type, scales = "free_y") +
  scale_fill_brewer(palette = "Spectral") +
  scale_color_brewer(palette = "Spectral") +
  theme_minimal() +
  theme(legend.position = "top",
        legend.justification = "left",
        legend.title = element_text(face = "italic"),
        plot.background = element_rect(fill = "white"),
        panel.grid = element_blank(),
        axis.text = element_text(color = "grey10"),
        title = element_text(face = "bold"),
        strip.text = element_text(hjust = 0, face = "bold")) +
  labs(x = "Month", y = "Year-on-year difference",
       title = "Monthly year-on-year comparison",
       subtitle = "Difference in the number of acts adopted this year compared to last year")

Dataframe

Asd

Legislative efficiency

How long does it take for EU legislation to be adopted?

Hypothesis: enlargement leads to slower adoption processes due to increasing the number of players

Plot

Code

# calculate maximum length of adoption and discard negative
acts_days <- acts_proposals |> 
  filter(!is.na(celex),
         !is.na(date),
         !is.na(proposal)) |> 
  left_join(proposals, by = c("proposal"="celex")) |> 
  mutate(days = as.integer(as.Date(date) - as.Date(date_proposal))) |> 
  filter(days > -1) |> 
  group_by(celex) |> 
  filter(days == max(days)) |> 
  ungroup() |> 
  distinct(celex, .keep_all = TRUE) |> 
  arrange(date) |> 
  mutate(year_adopted = as.integer(str_sub(date, 1, 4)),
         type = str_sub(celex, 6, 6),
         type = case_when(type == "R" ~ "Regulation",
                          type == "L" ~ "Directive",
                          type == "D" ~ "Decision",
                          T ~ "Other"),
         five_year = cut_interval(year_adopted, length = 5)) |> 
  filter(year_adopted > 1985) # not many complete pairs before this

# identify outliers
outliers <- acts_days |> 
  group_by(type) |> 
  mutate(max = max(days),
         mean = mean(days),
         coef = days / mean) |> 
  filter(coef > 2.9)

# plot
acts_days |> 
  filter(!celex %in% outliers$celex) |> 
  group_by(type) |> 
  mutate(global_mean = mean(days)) |> 
  ggplot(aes(x = five_year, y = days, color = type, fill = type)) +
  geom_boxplot(alpha = 0.1) +
  geom_hline(aes(yintercept = global_mean), lty = 2, color = "grey70") +
  facet_wrap(~type, dir = "h", ncol = 1, scales = "free_y") +
  scale_fill_brewer(palette = "Spectral") +
  scale_color_brewer(palette = "Spectral") +
  theme_minimal() +
  theme(legend.position = "top",
        legend.justification = "left",
        legend.title = element_text(face = "italic"),
        plot.background = element_rect(fill = "white"),
        panel.grid = element_blank(),
        axis.text = element_text(color = "grey10"),
        title = element_text(face = "bold"),
        plot.caption = element_text(face = "italic", size = 8),
        strip.text = element_text(hjust = 0, face = "bold")) +
  labs(x = NULL,
       y = "Days to adoption",
       title = "Number of days between proposal and adoption",
       subtitle = "By five-year window in which the act was adopted*",
       caption = "* Outliers are pruned from the analysis, dashed line shows the global mean")

Dataframe

Directives take by far the longest to adopt; unlike decisions and regulations, they are rarely deployed to regulate trivial matters. Nonetheless, there are few easily discernible temporal patterns in the plot. Most of all, it is not obvious that legislative efficiency would decrease as the EU expanded its membership.

However, before moving onto further investigation of the efficiency hypothesis, we should pause and take a look at some of the discarded outliers. What legislation took the longest to adopt?

Regression

Regression analysis unpacks the relationship between a response (dependent) variable and one or more explanatory variables. In our case, we are interested in the relationship (or absence thereof) between the number of days it takes the EU to turn a Commission proposal into law and the number of Member States.

We will assume the relationship between the variables is functionally linear.

Table

Linear regression model of legislative efficiency (Y_i = days)
Baseline Controls Zero Intercept
Number of Member States 5.569*** 6.318*** 6.318***
(0.526) (0.511) (0.511)
Constant 165.590***
(10.501)
Act type = Other 151.951*** 151.951***
(32.580) (32.580)
Act type = Regulation −61.756* 90.195***
(31.008) (9.814)
Act type = Decision −61.844* 90.106***
(31.078) (12.406)
Act type = Directive 441.576*** 593.526***
(32.076) (13.311)
Num.Obs. 14042 14042 14042
R2 0.008 0.155 0.396
R2 Adj. 0.008 0.155 0.396
AIC 209835.3 207590.0 207590.0
BIC 209858.0 207635.3 207635.3
Log.Lik. −104914.668 −103788.984 −103788.984
F 112.107 643.150 1844.200
RMSE 425.23 392.51 392.51
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
In the ‘Controls’ specification, ‘Other’ is the reference category.

Plot

Code

# number of member states over time
ms_years <- bind_rows(
  data.frame(
    year = 1952:1972,
    n_ms = 6
  ),
  data.frame(
    year = 1973:1980,
    n_ms = 9
  ),
  data.frame(
    year = 1981:1985,
    n_ms = 10
  ),
  data.frame(
    year = 1986:1994,
    n_ms = 12
  ),
  data.frame(
    year = 1995:2004,
    n_ms = 15
  ),
  data.frame(
    year = 2005:2006,
    n_ms = 25
  ),
  data.frame(
    year = 2007:2013,
    n_ms = 27
  ),
  data.frame(
    year = 2014:2018,
    n_ms = 28
  ),
  data.frame(
    year = 2019:2025,
    n_ms = 27
  )
)

# append N member states
days_ms <- acts_days |> 
  left_join(ms_years, by = c("year_adopted"="year")) |> 
  mutate(type = relevel(as.factor(type), ref = "Other"))

# regress
models <- list()

## m0
models[['Baseline']] <- lm(
  data = days_ms,
  formula = days ~ n_ms
)
names(models[['Baseline']]$coefficients)[1] <- "Constant"

## m1
models[['Controls']] <- lm(
  data = days_ms,
  formula = days ~ n_ms + type
)
names(models[['Controls']]$coefficients)[1] <- "Act type = Other"

## m2
models[['Zero Intercept']] <- lm(
  data = days_ms,
  formula = days ~ n_ms + type + 0
)

# effect size
effect <- as.numeric(round(models[['Controls']]$coefficients[which(names(models[['Controls']]$coefficients) == "n_ms")],2))

# summary for p values
mod_sum <- summary(models[['Controls']])

# p value
p_value <- mod_sum$coefficient[names(mod_sum$coefficient[,1]) == "n_ms",4]

# p threshold
p_thresh <- 0.05

# can we reject the null
reject_null <- ifelse(effect > 0 & p_value < p_thresh, "can reject", "cannot reject")

# show table
modelsummary(models, 
             stars = TRUE,
             coef_map = c("n_ms" = "Number of Member States",
                          "Constant" = "Constant",
                          "typeOther" = "Act type = Other",
                          "Act type = Other" = "Act type = Other",
                          "typeRegulation" = "Act type = Regulation",
                          "typeDecision" = "Act type = Decision",
                          "typeDirective" = "Act type = Directive"),
             title = "Linear regression model of legislative efficiency (Y_i = days)",
             notes = list("In the 'Controls' specification, 'Other' is the reference category."))

Dataframe

Controlling for the type of act, the linear model predicts that for every additional Member State, the EU takes 6.32 days longer to adopt the legal act.

This model is far too simple to establish the existence of a causal relationship between the number of Member States and legislative efficiency. Indeed, some scholars would argue that no amount of conditioning on observables is sufficient to establish causality.

Nonetheless, our simple analysis suggests that we can reject the null hypothesis that more Member States in the EU system are not associated with slower law-making.